﻿#include <QMutex>
#include "common.h"
#include "LogRecord.h"
#include "CfgReader.h"
#include "LogInfo.h"


#ifdef Q_OS_WIN
#include <windows.h> 
#endif

// 日志文件最大字节数 10M
#define LOG_MAX_BYTE_SIZE 10*1024*1024
// 日志文件数量 默认五个
#define LOG_FILE_COUNT 5;

// 最大文件大小
quint32 g_LogMaxFileZize = LOG_MAX_BYTE_SIZE;
// 日志文件数量
quint32 g_LogFileCount = LOG_FILE_COUNT;

// 互斥锁
static QMutex g_mutex;



// 文件锁
static QMutex g_file_mutex;

void LogRecord::logMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    g_mutex.lock();

#ifdef Q_OS_WIN
    // 输出到编译器输出窗口
    OutputDebugString(msg.toStdWString().c_str());
    OutputDebugStringA("\n");
#else 
    fprintf(stderr, "%s\n", msg.toLocal8Bit().data());
    fflush(stderr);
#endif
    // 日志配置信息
    static CfgReader reader;
    LogInfo* pLogInfo = reader.getLogInfo();

    if (!pLogInfo)
    {
        g_mutex.unlock();
        return;
    }

    // 未打开日志
    if (!pLogInfo->getEnable())
    {
        g_mutex.unlock();
        return;
    }

    g_LogMaxFileZize = pLogInfo->getLogFileSize() * 1024 * 1024;

	g_LogFileCount = pLogInfo->getFileCount();

    // 转换后的日志等级 日志等级：0-4； 0：最详尽
    int nTransLogLevel = transformLogLevel(type);

    int nCfgLevel = pLogInfo->getLogLevel();
    // 如果日志等级等级小于配置文件设置则过滤 
    if (nTransLogLevel < nCfgLevel)
    {
        g_mutex.unlock();
        return;
    }

    QString strMsg;

    switch (nTransLogLevel)
    {
    case LL_Debug: //调试信息
    {
        strMsg = QString("Debug");
        writeFile(strMsg, context, msg);
    }
    break;
    case LL_Info: // 一般信息
    {
        strMsg = QString("Info");
        writeFile(strMsg, context, msg);
    }
    break;
    case LL_Warning: // 警告信息
    {
        strMsg = QString("Warning");
        writeFile(strMsg, context, msg);
    }
    break;
    case LL_Error: // 严重信息
    {
        strMsg = QString("Error");
        writeFile(strMsg, context, msg);
    }
    break;
    case LL_Fatal: // 程序错误信息
    {
        strMsg = QString("Fatal");
        writeFile(strMsg, context, msg);
        abort();
    }
    break;
    default:
    {}
    break;
    }
    // 解锁
    g_mutex.unlock();
}

QFile* LogRecord::createLogFile()
{
    QFile* pFile = nullptr;

	g_file_mutex.lock();
    QString strDir = QCoreApplication::applicationDirPath() + "/../logs/";
    QDir dir(strDir);
    if (!dir.exists())
    {
        dir.mkdir(strDir);
    }

    QString strLogFile = strDir + "log_" + CONST_LOG_FILE_NAME;

	if (g_LogFileCount == 1)
	{
		strLogFile = strLogFile.arg("");
		QFile::remove(strLogFile);
	}
	else
	{
		for (int i = g_LogFileCount; i > 0; --i)
		{
			QString strTemp = QString("_%1")
				.arg(QString::number(i));
			QString strTempLogFile = strLogFile.arg(strTemp);

			if (i == 5)
			{
				if (QFile::exists(strTempLogFile))
				{
					QFile::remove(strTempLogFile);
				}
			}
			else
			{
				if (QFile::exists(strTempLogFile))
				{
					strTemp = QString("_%1")
						.arg(QString::number(i + 1));
					QString strNewFileName = strLogFile.arg(strTemp);
					QFile::rename(strTempLogFile, strNewFileName);
				}
				if (i == 1)
				{
					strTemp = strDir + "log_" + CONST_LOG_FILE_NAME;
					strTemp = strTemp.arg("");

					QFile file(strTemp);
					if (file.size() > g_LogMaxFileZize)
					{
						QString strTemp1 = QString("_%1").arg(QString::number(1));

						QString strNewFileName = strLogFile.arg(strTemp1);
						QFile::rename(strTemp, strNewFileName);
					}
				}
			}
		}
	}

	strLogFile = strDir + "log_" + CONST_LOG_FILE_NAME;
	strLogFile = strLogFile.arg("");

    pFile = new QFile(strLogFile);
    if (!pFile->open(QIODevice::ReadWrite | QIODevice::Append))
    {
		qDebug() << "open file failed:" << pFile->errorString();
    }
	g_file_mutex.unlock();
    return pFile;
}

QFile* LogRecord::getLogFile()
{
    static QFile* pFile = nullptr;

    // 如果不空则判断日志文件大小
    if (pFile)
    {
        quint64 nSize = pFile->size();

        if (nSize > g_LogMaxFileZize)
        {
            pFile->close();
            delete pFile;
            pFile = nullptr;

            pFile = createLogFile();
        }
    }
    else // 创建一个新的日志文件
    {
        pFile = createLogFile();
    }
    return pFile;
}

void LogRecord::writeFile(QString msgType, const QMessageLogContext &context, const QString& msg)
{
    // 设置输出信息格式
    QString strTime = QDateTime::currentDateTime().toString(CONST_SEPARATE_DATA_TIME_MSEC_FORMAT);
    QString strMessage = QStringLiteral("%1 %2 %3 MSG:%4")
        .arg(msgType)
        .arg(strTime)
        .arg(context.function)
        .arg(msg);
    // 获取读写文件
    QFile* pFile = getLogFile();
    if (pFile)
    {
        QTextStream stream(pFile);
        stream << strMessage << "\r\n";
        pFile->flush();
    }
}

int LogRecord::transformLogLevel(QtMsgType type)
{
    int nLevel = 0;
    // 调试信息
    if (type == QtDebugMsg)
    {
        nLevel = QtDebugMsg;
    }
    // 运行告警错误等
    else if (type == QtWarningMsg || type == QtCriticalMsg || type == QtFatalMsg)
    {
        nLevel = type;
        nLevel += 1;
    }
    // 运行信息
    else if (type == QtInfoMsg)
    {
        nLevel = 1;
    }

    return nLevel;
}

int GenerateMiniDump(PEXCEPTION_POINTERS pExceptionPointers)
{
	// 创建 dmp 文件件
	WCHAR szFileName[MAX_PATH] = { 0 };
	TCHAR szVersion[] = L"DUMP";
	SYSTEMTIME stLocalTime;
	GetLocalTime(&stLocalTime);
	wsprintf(szFileName, L"%s-%04d%02d%02d-%02d%02d%02d.dmp", szVersion, stLocalTime.wYear,
		stLocalTime.wMonth, stLocalTime.wDay,
		stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond);
	HANDLE hDumpFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE
		| FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
	if (INVALID_HANDLE_VALUE == hDumpFile)
	{
		return EXCEPTION_CONTINUE_EXECUTION;
	}
	// 写入 dmp 文件
	MINIDUMP_EXCEPTION_INFORMATION expParam;
	expParam.ThreadId = GetCurrentThreadId();
	expParam.ExceptionPointers = pExceptionPointers;
	expParam.ClientPointers = FALSE;
	MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpWithFullMemoryInfo,
		(pExceptionPointers ? &expParam : NULL), NULL, NULL);
	// 释放文件
	CloseHandle(hDumpFile);
	return EXCEPTION_EXECUTE_HANDLER;
}

LONG LOGRECORD_EXPORT ExceptionFilter(LPEXCEPTION_POINTERS lpExceptionInfo)
{
	// 这里做一些异常的过滤或提示
	if (IsDebuggerPresent())
	{
		return EXCEPTION_CONTINUE_SEARCH;
	}
	return GenerateMiniDump(lpExceptionInfo);
}
